ident = '$Id: build_ui.py 104 2006-05-25 22:06:43Z ahmad $'

'''App File Builder'''
import sys
import wx
import rex
import common
import images
import re
import base
from xml.sax 	import saxutils
from xml.dom	import minidom
from xml.dom.minidom import getDOMImplementation

CG_LIST = 100
RB_SUBS = 101
RB_LIST = 102
CB_ARCHIVE=103
ID_BUILD=104
ID_OBJECT_PASTE=105
ID_OBJECT_REMOVE=106



class binder_options_dialog( wx.Dialog ):
	"""
	Dialog to ask the user whether to include:
	  All Instances
	  All Variables
	"""
	def __init__( self, parent ):
		wx.Dialog.__init__( self, parent, -1, "Binder Options", wx.DefaultPosition, wx.Size( 350, 250 ), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.DIALOG_EX_METAL  )
		common.metalic( self )
		self.chooser = wx.CheckListBox(self, -1, choices=['Include all instances', 'Include all variables'])
		self.all_instances = False
		self.all_variables = False
		# Add the buttons
		self.button_sizer = wx.BoxSizer( wx.HORIZONTAL )
		self.build_btn = wx.Button( self, wx.ID_OK, "&Ok" )
		#wx.EVT_BUTTON( self, wx.ID_OK, self.OnOk )
		self.button_sizer.Add( (0,0), 1, 1 )
		self.button_sizer.Add( self.build_btn, 1, 0 )
		self.button_sizer.Add( (0,0), 1, 1 )
		btn = wx.Button( self, wx.ID_CANCEL, "&Close" )
		btn.SetDefault()
		self.button_sizer.Add( btn, 1, 0 )
		self.button_sizer.Add( (0,0), 1, 1 )
		# Add the Drag Box
		self.sizer = wx.BoxSizer( wx.VERTICAL )
		self.status_label = wx.StaticText( self, -1, "")
		self.sizer.Add( self.status_label, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5 )
		self.sizer.Add( self.chooser, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5 )
		self.sizer.Add( self.button_sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 5 )
		self.SetSizer( self.sizer )
		self.SetAutoLayout( 1 )
		# Windows needs this call
		self.Layout()

class suiBuilderDropTarget( wx.PyDropTarget ):
	def __init__( self, list_object, sui ):
		wx.PyDropTarget.__init__( self )
		self.list 	= list_object
		self.sui	= sui
		self.data   = wx.PyTextDataObject(" ")
		self.SetDataObject( self.data )

	def OnEnter( self, x, y, d ):
		return wx.DragCopy

	def OnLeave( self ):
		pass

	def OnDrop( self, x, y ):
		return self.list.IsEnabled()

	def OnDragOver( self, x, y, d ):
		if self.list.IsEnabled():
			return wx.DragCopy
		else:
			return wx.DragNone
			
	def OnData( self, x, y, d ):
		common.dprint ( "OnData: %d %d %d\n" % (x, y, d) )
		if self.GetData():
			data = self.data.GetText()			
			common.dprint("Dropped data", data )
			if self.sui.handle_dropped_pasted( data ):
				return wx.DragCopy
		return wx.DragNone

class build( wx.Dialog ):
	"""
	App Builder Dialog
	Accepts dropped objects from the rest of the EDE system and builds
	them into a mini-app for deployment to other servers or sale in the Etelos Store
	"""
	def return0():
		return 0

	def __init__( self, parent, account ):
		title = "App Builder"
		wx.Dialog.__init__( self, parent, -1, title, wx.DefaultPosition, wx.Size( 450, 550 ), style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.DIALOG_EX_METAL  )
		common.metalic( self )
		self.pid = 0			# the pid of the running job on the server
		self.objects = {}		# List of objects to publish to
		self.account = account
		self.current_id = 0
		sizer = wx.BoxSizer( wx.VERTICAL )
		# Create GridBag
		asizer = wx.GridBagSizer( 2, 2 )
		# Add the Titles
		asizer.Add( wx.StaticText( self, -1, "ESDnet Account Info:" ), (0,0), (1,2), wx.LEFT | wx.RIGHT, 20 )
		asizer.Add( wx.StaticText( self, -1, "App Info:" ),(0,3), (1,2),  0, wx.LEFT | wx.RIGHT, 20 )
		#Add Esdnet Info
		self.esdnet_username = wx.TextCtrl( self, -1 )
		self.esdnet_password = wx.TextCtrl( self, -1, style = wx.TE_PASSWORD )
		asizer.Add( wx.StaticText( self, -1, "Username:",style=wx.ALIGN_RIGHT ), (1,0), (1,1),  wx.EXPAND )
		asizer.Add( self.esdnet_username,(1,1), (1,1),  wx.EXPAND )
		asizer.Add( wx.StaticText( self, -1, "Password:",style=wx.ALIGN_RIGHT ), (2,0), (1,1),  wx.EXPAND )
		asizer.Add( self.esdnet_password, (2,1), (1,1),  wx.EXPAND )
		#Add App Info
		asizer.Add( wx.StaticText( self, -1, "App Name:",style=wx.ALIGN_RIGHT ), (1,3), (1,1), wx.EXPAND )
		self.appname = wx.TextCtrl( self, -1 )
		asizer.Add( self.appname, (1,4), (1,1), wx.EXPAND )

        # Replace the wholeapp checkbox with a Label when not allowed
		allowed = self.account.execute( 'base', "wholeapp_check", params="" )
		allowed = int(allowed.strip())
		if allowed:
			self.wholeaccount = wx.CheckBox( self, CB_ARCHIVE, "Build whole account")
		else:
			self.wholeaccount = wx.StaticText( self, -1, "Wholeapp Not Licensed", style=wx.ALIGN_RIGHT)
			self.wholeaccount.IsChecked = lambda : 0
		asizer.Add( self.wholeaccount, (2,3), (1,2), wx.LEFT | wx.RIGHT, 20 )
		sizer.Add( asizer, 0, wx.EXPAND | wx.ALL, 10 )

		# The list
		self.list = wx.ListCtrl( self, CG_LIST, style=wx.LC_REPORT )
		self.il = wx.ImageList(16, 16)
		# Load our images
		self.contact_icon 	= self.il.Add( images.getContactBitmap() )
		self.group_icon 	= self.il.Add( images.getGroupBitmap() )
		self.binder_icon 	= self.il.Add( images.getBinderBitmap() )
		self.variable_icon = self.il.Add( images.getVariableBitmap() )
		self.instance_icon = self.il.Add( images.getInstanceBitmap() )
		self.mirror_binder_icon = self.il.Add( images.getMirrorBinderBitmap() )
		self.system_binder_icon = self.il.Add( images.getSystemBinderBitmap() )
		self.list.SetImageList( self.il, wx.IMAGE_LIST_SMALL )
		sizer.Add( self.list, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 20 )
		info = wx.ListItem()
		info.m_mask = wx.LIST_MASK_TEXT # | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT
		#info.m_image = -1
		info.m_format = 0
		info.m_text = "ID"
		self.list.InsertColumnInfo(0, info)
		info.m_text = "Name"
		self.list.InsertColumnInfo(1, info)
		info.m_text = "Kind"
		self.list.InsertColumnInfo(2, info)
		info.m_text = "Extra"
		self.list.InsertColumnInfo(3, info)
		# Set the drop target
		dt = suiBuilderDropTarget( self.list, self )
		self.list.SetDropTarget( dt )
		wx.EVT_RIGHT_DOWN( self.list, self.OnListRightClick )
		# Set the drag and drop events
		wx.EVT_LIST_BEGIN_DRAG( self.list, CG_LIST, self.OnBeginDrag )
		self.resize_columns()
		self.use_list = True
		# Add the buttons
		self.button_sizer = wx.BoxSizer( wx.HORIZONTAL )
		self.build_btn = wx.Button( self, ID_BUILD, "&Build" )
		wx.EVT_BUTTON( self, ID_BUILD, self.OnBuild )
		self.button_sizer.Add( (0,0), 1, 1 )
		self.button_sizer.Add( self.build_btn, 1, 0 )
		self.button_sizer.Add( (0,0), 1, 1 )
		btn = wx.Button( self, wx.ID_CANCEL, "&Close" )
		btn.SetDefault()
		self.button_sizer.Add( btn, 1, 0 )
		self.button_sizer.Add( (0,0), 1, 1 )
		# Add the Drag Box
		self.sizer = wx.BoxSizer( wx.VERTICAL )
		self.sizer.Add( sizer, 1, wx.EXPAND | wx.NORTH | wx.SOUTH | wx.EAST | wx.WEST, 10 )

		self.status_label = wx.StaticText( self, -1, "")
		self.sizer.Add( self.status_label, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 20 )
		self.sizer.Add( self.button_sizer, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 20 )
		self.SetSizer( self.sizer )
		self.SetAutoLayout( 1 )
		menu = wx.Menu()
		menu.Append( ID_OBJECT_PASTE, "&Paste", " Paste contacts or groups from the clipboard." )
		menu.Append( ID_OBJECT_REMOVE, "&Cut", " Remove the selected contacts or groups" )
		self.menu = menu
		wx.EVT_MENU( self, ID_OBJECT_PASTE, self.OnObjectPaste )
		wx.EVT_MENU( self, ID_OBJECT_REMOVE, self.OnObjectRemove )
		# Windows needs this call
		self.Layout()

	def OnDestroy(self,event):
		common.dprint("Destroying Build UI")
		self.account.window.build_dialog = None
		
	def OnListRightClick( self, event ):
		common.dprint("OnListRightClick")
		try:
			self.PopupMenu( self.menu, wx.Point( event.m_x + 5, event.m_y + 45) )
		except:
			pass

	def OnObjectPaste( self, event ):
		wx.TheClipboard.Open()
		clipdata = wx.TextDataObject()
		wx.TheClipboard.GetData( clipdata )
		wx.TheClipboard.Close()
		xmlstring = clipdata.GetText()
		self.handle_dropped_pasted( xmlstring )

	def OnObjectRemove( self, event ):
		sel = common.get_selected_in_list( self.list )
		if len( sel ) < 1: return
		for item in sel:
			oid  = self.list.GetItemData( item )
			try:
				self.objects.__delitem__( oid )
				self.list.DeleteItem( item )
			except:
				pass

	def OnUseSubs( self, event ):
		self.list.Enable( False )
		self.use_list = False

	def OnUseList( self, event ):
		self.list.Enable( True )
		self.use_list = True

	def expandFolder(self, fname, recursive = True):
		
		# Fname is such a pattern that a prepended slash will result in
		# it's path as if the root were the webdav document root.
		
		# Spaghetti code warning! This one is the fault of Darien Hager
		# darien@etelos.com Mar 2007
		
		# Justification: Having the build UI use webdav information to expand
		# a drag-n-drop folder is FAR better than rewriting all the other drag
		# -and-drop functionality.
		
		# Originally thought of defining a new XML tag for the final output to
		# the server, but then people can't select all items but exclude one,
		# etc.
		
		retfiles = []
		davCon = self.GetParent().dav_control.davCon
		(subdirs, files) = davCon.get_dav_items('/'+str(fname))
		
		for davobj in files:
			# Strip leading slash, for compat with build stuff
			uri = davobj.uri.lstrip('/')
			retfiles.append(uri)										
		if(recursive and (len(subdirs) > 0)):			
			for davobj in subdirs:
				subpath = davobj.uri.lstrip('/')
				retfiles.extend(self.expandFolder(subpath))						
		return retfiles
		
		
	def OnBuild( self, event ):
		"""
		Start the build process, using the objects dropped into the list
		"""
		if self.pid > 0:
			self.CheckStatus()
			return

		common.dprint("Building ", self.objects )
		appname = self.appname.GetValue().strip()
		if appname == "":
			wx.MessageBox( "You must provide an App Name",
						"App Name error", wx.OK )
			return
		# change the app name
		appname = re.sub(r"[^\w_-]", "_", appname)
		esdnet_username = self.esdnet_username.GetValue().strip()
		esdnet_password = self.esdnet_password.GetValue().strip()
		if esdnet_username == "" or esdnet_password == "":
			wx.MessageBox( "You must provide an ESDNet Username and Password",
						"ESDNet account error", wx.OK )
			return
		# check the esdnet username and password
		edom = '<esdnet_authenticate esdnet_username="%s" esdnet_password="%s" />' % 	(esdnet_username, esdnet_password)
		euuid = self.account.execute( 'base', "esdnet_authenticate", dom=edom )
		common.dprint("RETURN ", euuid )
		if re.match("[\w]{32}", euuid) is None:
			wx.MessageBox( "ESDNet authentication failed",
						"ESDNet account error", wx.OK )
			return
		#Create XML
		impl = getDOMImplementation()
		#Create XML ROOT
		xmldoc = impl.createDocument(None, "ROOT", None)
		root = xmldoc.documentElement
		root.setAttribute("name",appname)
		#Create XML BUILD
		build = xmldoc.createElement('BUILD')
		build.setAttribute("phase","creating")
		build.setAttribute("esdnet_username",esdnet_username)
		build.setAttribute("esdnet_password",esdnet_password)
		build.setAttribute("server_name",self.account.server)
		root.appendChild(build)
		if self.wholeaccount.IsChecked():
			build.setAttribute("whole_app","yes")
		else:
			build.setAttribute("whole_app","no")
			for chosen in self.objects:
				common.dprint("Object ",self.objects[chosen])
				type = self.objects[chosen][0]
				id = self.objects[chosen][1]
				name = self.objects[chosen][2]
				extra = self.objects[chosen][3]
				common.dprint("type ", type )
				common.dprint("id ", id )
				common.dprint("name ", name )
				common.dprint("extra ", extra )
				if type == "contact":
					build = xmldoc.createElement('CONTACT')
					build.setAttribute("id",id)
					build.setAttribute("uuid",name)
					root.appendChild(build)
				elif type == "group":
					build = xmldoc.createElement('GROUP')
					build.setAttribute("id",id)
					build.setAttribute("name",name)
					root.appendChild(build)
				elif type == "publication":
					build = xmldoc.createElement('PUBLICATION')
					build.setAttribute("id",id)
					build.setAttribute("name",name)
					root.appendChild(build)
				elif type == "issue":
					build = xmldoc.createElement('ISSUE')
					build.setAttribute("id",id)
					build.setAttribute("pid","")
					root.appendChild(build)
				elif type == "binder":
					build = xmldoc.createElement('BINDER')
					build.setAttribute("id",id)
					build.setAttribute("name",name)
					all_instances,all_variables = extra
					if all_instances:
						build.setAttribute("with_all_instances","1")
					else:
						build.setAttribute("with_all_instances","0")
					if all_variables:
						build.setAttribute("with_all_variables","1")
					else:
						build.setAttribute("with_all_variables","0")
					root.appendChild(build)
				elif type == "variable":
					build = xmldoc.createElement('VARIABLE')
					build.setAttribute("binder",id)
					build.setAttribute("name",name)
					root.appendChild(build)
				elif type == "instance":
					build = xmldoc.createElement('INSTANCE')
					build.setAttribute("id",id)
					build.setAttribute("bid",name)
					root.appendChild(build)
				elif type == "gcb":
					build = xmldoc.createElement('GCB')
					build.setAttribute("id",id)
					build.setAttribute("name",name)
					root.appendChild(build)
				elif type == "file":
					build = xmldoc.createElement('FILE')
					build.setAttribute("location",name)
					root.appendChild(build)				
		cdom = xmldoc.toxml()
		common.dprint("XML ", cdom )
		pid = self.account.execute( 'base', "buildapp", DOM=cdom )
		pid = int(pid)
		common.dprint("returned pid ", pid )
		if pid < 1:
			wx.MessageBox( "Build Job failed",
						"Build Job failed, please contact the administrator.", wx.OK )
			return
		self.pid = pid
		# relabel the build button
		self.build_btn.SetLabel("Status")
		self.status_label.SetLabel("Build started ...")
		self.Layout()

		# Setup a timer to do updates automagically
		self.timer = wx.Timer(self)
		self.Bind( wx.EVT_TIMER, self.CheckStatus(), self.timer)
		self.timer.Start(1000)

	def CheckStatus(self):
		"""
		Check the build status and update the status_label
		"""
		common.dprint("checking pid ", self.pid )
		still_running = self.account.execute( 'base', "still_running", pid=self.pid )
		still_running = still_running.strip()
		common.dprint("still running ", still_running )
		if still_running == '1':
			self.status_label.SetLabel("Processing ...")
		else:
			self.status_label.SetLabel("Done.")
			self.build_btn.SetLabel("Build")
			self.pid = 0
			self.timer.Stop()
		
	def OnBeginDrag( self, event ):
		list = self.list
		sel = common.get_selected_in_list( list )
		if len( sel ) < 1: return
		oid  = list.GetItemData( sel[0] )
		data = wx.PyTextDataObject(" ")
		ds = wx.DropSource( self )
		ds.SetData( data )
		result = ds.DoDragDrop()
		if result == 5:
			# Remove the object
			try:
				self.objects.__delitem__( oid )
				list.DeleteItem( sel[0] )
			except:
				pass

	def AppendObject( self, type, id, name ):
		common.dprint( "AppendObject type=%s id=%s name=%s" % ( type, id, name ) )
		extra = ""
		xmlextra = "0"
		x = self.list.GetItemCount()
		if type == "contact":
			icon = self.contact_icon
		elif type == "group":
			icon = self.group_icon
		elif type == "binder":
			icon = self.binder_icon
			if type == "binder":
				dlg = binder_options_dialog( self )				
				res = dlg.ShowModal()
				all_instances = dlg.chooser.IsChecked(0)
				all_variables = dlg.chooser.IsChecked(1)
				dlg.Destroy()
				del( dlg )
				if res != wx.ID_OK:
					return
				extra = ''
				if all_instances:
					extra = " All Instances "
				if all_variables:
					extra = extra + " All Variables "
				xmlextra = ( all_instances, all_variables )
		elif type == "instance":
			icon = self.instance_icon
		else:
			icon = self.group_icon

		# Check the licensing status of this object
		if type == 'instance':
		    check_id = '.'.join((name,id))
		elif type == 'file':
			check_id = name
		else:
		    check_id = id
		self.status_label.SetLabel("Checking License for %s:%s" % (type, check_id))
		allowed = self.account.execute( 'base', "license_check", params="%s:%s" % (type, check_id) )
		allowed = int(allowed.strip())
		common.dprint("License check: ", allowed )
		if allowed == 1:
			self.status_label.SetLabel("License for %s:%s is ALLOWED" % (type, check_id))
		else:
			self.status_label.SetLabel("License for %s:%s is DENIED" % (type, check_id))
			return

		common.dprint("Adding ", type, "id ", id, " name", name )
		x = self.list.InsertImageStringItem( sys.maxint, id, icon )
		self.list.SetStringItem( x , 1, name )
		self.list.SetStringItem( x , 2, type )
		self.list.SetStringItem( x , 3, extra )
		oid = self.get_new_id()
		self.objects[oid] = [type,id,name,xmlextra]
		self.list.SetItemData( x, oid )

	def handle_dropped_pasted( self, data ):
		common.dprint("Handle data", data )		
		xmlstring = data.strip()		
		common.dprint("XML STRING is : [%s]" % xmlstring )
		#parse the data
		sdom  = None
		snode = None
		datatype = "XML"
		try:
			sdom 	= minidom.parseString( xmlstring )
			snode	= sdom.firstChild
		except:
			datatype = "STRING"
		
		common.dprint(datatype);
		
		if datatype == "XML":
			common.dprint("Looking up ", snode.nodeName )
			common.dprint("XML ", snode.toxml() )
			for inode in snode.getElementsByTagName("contact"):
				type = "contact"
				id = inode.getAttribute("id")
				name = inode.getAttribute("uuid")
				self.AppendObject( type, id, name )
			for inode in snode.getElementsByTagName("group"):
				type = "group"
				id = inode.getAttribute("id")
				name = inode.getAttribute("name")
				self.AppendObject( type, id, name )
			if snode.nodeName == "publication":
				type = "publication"
				id = snode.getAttribute("id")
				name = snode.getAttribute("name")
				self.AppendObject( type, id, name )
			if snode.nodeName == "gcb":
				type = "gcb"
				id = snode.getAttribute("id")
				name = snode.getAttribute("rule") + " = " + snode.getAttribute("instances")
				self.AppendObject( type, id, name )
		elif datatype == "STRING":
			ins = re.match(r"(?P<bid>[0-9]*)\.(?P<id>[0-9]*)\|(?P<bname>[-a-zA-Z0-9_ ]*)\.(?P<iname>[-a-zA-Z0-9_ ]*)", data)
			bin = re.match(r"(?P<name>[-a-zA-Z0-9_ ]*)\.(?P<id>[0-9]*)", data)
			iss = re.match(r"<#\[(PAGE|EMAIL|ISSUE)_URL_(?P<id>[0-9]*)\]#>", data)
			var = re.match(r"<#\[(?P<binder>[-a-zA-Z0-9_ ]*)\.(?P<name>[-a-zA-Z0-9_ ]*)\]#>", data)
			afile = re.match(r"^\/(?P<name>[^;]*)", data)
			
			if ins:				
				self.AppendObject('instance',ins.group('id'),ins.group('bid'))
			elif bin:
				self.AppendObject('binder', bin.group("id"),bin.group("name"))
			elif iss:				
				self.AppendObject('issue', iss.group("id"), '' )
			elif var:				
				self.AppendObject('variable', var.group("binder"), var.group("name") )
			elif afile:
				# Includes both files are directories. Directories end in slashes
				
				if(afile.group("name").endswith("/")):
					# Folder, add multiple files
					pathstub = afile.group("name")
					
					choices = {'Only first level':False,'All contained files':True}										
					dlg = wx.SingleChoiceDialog(self, pathstub+'\nWhat items under this folder do you wish to add to the list?', 'Folder Selected',
					choices.keys(), 
					wx.CHOICEDLG_STYLE
					)
					
					if dlg.ShowModal() == wx.ID_OK:
						#true or false for recursion
						choice = choices[dlg.GetStringSelection()]
					else:
						choice = None
					dlg.Destroy()
					if(choice != None):
						files = self.expandFolder(pathstub, choice)
						dialog = wx.ProgressDialog( "Adding files", "Time remaining", len(files),
													style=wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME)
						counter = 0
						for f in files:
							counter = counter + 1
							dialog.Update(counter)
							self.AppendObject('file','', f )
						dialog.Destroy()
				else:
					# Single file
					self.AppendObject( 'file', '', afile.group("name") )
				
		self.resize_columns()
		return True

	def get_new_id( self ):
		self.current_id = self.current_id + 1
		return self.current_id

	def resize_columns( self ):
		for i in range( self.list.GetColumnCount() ):
			self.list.SetColumnWidth( i, wx.LIST_AUTOSIZE )
